using System;
using System.Collections.Generic;
using FireStarter.Base;
using FireStarterModeller.BO;

namespace FireStarter.CodeGeneration.Generator
{
    /// <summary>
    /// Provides a delegate to return a custom value for a specified
    /// term encountered in a template
    /// </summary>
    /// <param name="modelElement">The model element that has the current scope
    /// when the term is encountered.  You can access parent classes or projects
    /// through the Parent property on the element.</param>
    /// <returns>Returns the value to be inserted in the template or to be interpreted
    /// by logic</returns>
    public delegate object ValueDelegate(IModelElement modelElement);

    /// <summary>
    /// Manages a collection of delegates assigned to specific terms
    /// encountered in a code generation template.  Use these delegates when
    /// you need to assign custom behaviour and do not want to create or amend
    /// an interface or create a new action parser (which returns text and not values).
    /// </summary>
    /// <remarks>
    /// This class wraps a dictionary and stores all the delegate names in upper case.
    /// This is used to support case insensitivity, which was a stated option for the
    /// templates.
    /// </remarks>
    public class ValueDelegatesDictionary
    {
        private readonly Dictionary<string, ValueDelegate> _delegateDictionary = new Dictionary<string, ValueDelegate>();

        public ValueDelegatesDictionary()
        {
            Add("Class_HasCompulsoryProperties", ClassHasCompulsoryPropertiesDelegate);
            Add("Property_HasDefaultValue", PropertyHasDefaultValueDelegate);
            Add("Property_IsClassID", PropertyIsClassIDDelegate);
            Add("Property_IsNonIDPrimaryKey", PropertyIsNonIDPrimaryKeyDelegate);
            Add("Property_IsInKey", PropertyIsInKeyDelegate);
            Add("ValidValue", ValidValueDelegate);
        }

        public void Add(string delegateName, ValueDelegate theDelegate)
        {
            _delegateDictionary.Add(delegateName.ToUpper(), theDelegate);
        }

        public bool ContainsKey(string delegateName)
        {
            return _delegateDictionary.ContainsKey(delegateName.ToUpper());
        }

        public int Count
        {
            get { return _delegateDictionary.Count; }
        }

        public ValueDelegate this[string delegateName]
        {
            get { return _delegateDictionary[delegateName.ToUpper()]; }
        }

        private static object ClassHasCompulsoryPropertiesDelegate(IModelElement modelElement)
        {
            Utilities.CheckDelegateContext("ClassHasCompulsoryProperties", modelElement, typeof(IModelClass));
            IModelClass dmClass = (IModelClass)modelElement;
            foreach (IModelProperty modelProperty in dmClass.Properties)
            {
                if (modelProperty.IsCompulsory) return true;
            }
            return false;
        }

        private static object PropertyHasDefaultValueDelegate(IModelElement modelElement)
        {
            Utilities.CheckDelegateContext("Property_HasDefaultValue", modelElement, typeof(IModelProperty));
            IModelProperty modelProperty = (IModelProperty)modelElement;
            if (modelProperty.DefaultValue != null) return true;

            return PropertyIsClassIDDelegate(modelProperty);
        }

        private static object PropertyIsClassIDDelegate(IModelElement modelElement)
        {
            return CheckPropertyInPrimaryKey("Property_IsClassID", modelElement, true);
        }

        private static object PropertyIsNonIDPrimaryKeyDelegate(IModelElement modelElement)
        {
            return CheckPropertyInPrimaryKey("Property_IsNonIDPrimaryKey", modelElement, false);
        }

        private static object CheckPropertyInPrimaryKey(string delegateName, IModelElement modelElement, bool mustBeObjectID)
        {
            Utilities.CheckDelegateContext(delegateName, modelElement, typeof(IModelProperty));
            IModelProperty modelProperty = (IModelProperty)modelElement;
            Utilities.CheckPropertyHasParent(modelProperty);

            IModelClass dmClass = (IModelClass)modelProperty.Parent;
            return CheckPropertyInPrimaryKey(dmClass, modelProperty, mustBeObjectID);
        }

        private static object CheckPropertyInPrimaryKey(IModelClass dmClass, IModelProperty modelProperty, bool mustBeObjectID)
        {
            if (!dmClass.HasPrimaryKeyWithProps) return false;
            if (mustBeObjectID == dmClass.PrimaryKeyIsID)
            {
                foreach (IModelProperty primaryKeyProperty in dmClass.PrimaryKeyProperties)
                {
                    if (primaryKeyProperty.PropertyName == modelProperty.PropertyName) return true;
                }
            }

            if (dmClass.HasSuperClass)
            {
                IModelClass superClass = dmClass.SuperClass;
                return CheckPropertyInPrimaryKey(superClass, modelProperty, mustBeObjectID);
            }

            return false;
        }

        private static object PropertyIsInKeyDelegate(IModelElement modelElement)
        {
            bool inIDPK = (bool) CheckPropertyInPrimaryKey("Property_IsClassID", modelElement, true);
            bool inNonIDPK = (bool)CheckPropertyInPrimaryKey("Property_IsClassID", modelElement, false);
            if (inIDPK || inNonIDPK) return true;

            IModelProperty modelProperty = (IModelProperty)modelElement;
            IModelClass dmClass = (IModelClass)modelProperty.Parent;

            foreach (IModelKey modelKey in dmClass.AlternateKeys)
            {
                foreach (IModelProperty keyProperty in modelKey.KeyProperties)
                {
                    if (keyProperty == modelProperty) return true;
                }
            }
            return false;
        }

        private static object ValidValueDelegate(IModelElement modelElement)
        {
            Utilities.CheckDelegateContext("ValidValue", modelElement, typeof(IModelProperty));
            IModelProperty modelProperty = (IModelProperty)modelElement;
            return ValidValueGenerator.GetValidValue(modelProperty);
        }
    }
}